using System;
using System.Collections.Generic;
using System.Reflection;
using System.IO;


namespace TaskPluginInterface
{

    /// <summary>
    /// Contains Utility methods for the <see cref="TaskPluginInterface"/>library.
    /// </summary>
    public static class Utilities
    {
        #region Private Variables
        
        private static readonly log4net.ILog _log = log4net.LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);

        #endregion Private Variables

        #region GetPluginFileList

        /// <summary>
        /// Gets a list of files within the specified folder that contains Plugins.
        /// </summary>
        /// <param name="folder">The folder in which you want to get a plug-in list.</param>
        /// <returns>A List of files that contain valid <c>ITaskPlugin</c>'s</returns>
        public static List<FileInfo> GetPluginFileList(string folder)
        {
            _log.DebugFormat(Properties.Resources.Msg_GetPluginFileList_Checking, folder);

            #region Variables

            List<FileInfo> plugins = new List<FileInfo>();

            #endregion Variables

            #region Data Validation
            
            // Check to make sure the folder exists
            if (Directory.Exists(folder) == false)
            {
                _log.DebugFormat(Properties.Resources.Msg_FolderDoesNotExists, folder);
                return plugins;
            }

            #endregion Data Validation

            foreach (string filename in Directory.GetFiles(folder))
            {
                if (FileContainsPlugins(filename))
                {
                    _log.DebugFormat(Properties.Resources.Msg_GetPluginFileList_PluginFound, filename);
                    plugins.Add(new FileInfo(filename));
                }
            }
            _log.DebugFormat(Properties.Resources.Msg_GetPluginFileList_PluginCount, plugins.Count);

            return plugins;
        }

        /// <summary>
        /// Gets a list of files within the specified folder that contains Plugins of the specified type
        /// </summary>
        /// <param name="folder">The folder in which you want to get a plug-in list.</param>
        /// <param name="pluginType">The <see cref="TaskPluginInterface.PluginType">Plugin Type</see> to return.</param>
        /// <returns>A List of files that contain valid ITaskPlugins</returns>
        public static List<FileInfo> GetPluginFileList(string folder, PluginType pluginType)
        {
            _log.DebugFormat(Properties.Resources.Msg_GetPluginFileList_Checking1, folder, pluginType);

            #region Variables

            List<FileInfo> plugins = new List<FileInfo>();

            #endregion Variables

            #region Data Validation

            // Check to make sure the folder exists
            if (Directory.Exists(folder) == false)
            {
                _log.DebugFormat(Properties.Resources.Msg_FolderDoesNotExists, folder);
                return plugins;
            }

            #endregion Data Validation

            foreach (string filename in Directory.GetFiles(folder))
            {
                if (FileContainsPlugins(filename, pluginType))
                {
                    _log.DebugFormat(Properties.Resources.Msg_GetPluginFileList_PluginFound1, filename, pluginType);
                    plugins.Add(new FileInfo(filename));
                }
            }
            _log.DebugFormat(Properties.Resources.Msg_GetPluginFileList_PluginCount1, plugins.Count, pluginType);

            return plugins;
        }

        #endregion GetPluginFileList

        #region FileContainsPlugins

        /// <summary>
        /// Returns a boolean to indicate if the specified filename contains plugin
        /// </summary>
        /// <param name="filename">The file name to check for plugins</param>
        /// <returns>A true if the file has at least one plugin, otherwise false.</returns>
        public static bool FileContainsPlugins(string filename)
        {
            _log.DebugFormat(Properties.Resources.Msg_FileContainsPlugins_Checking, filename);
            
            #region Variables

            List<string> plugins;

            #endregion Variables

            #region Data Validation

            // Check to see if the file exist
            if (!File.Exists(filename))
            {
                _log.DebugFormat(Properties.Resources.Msg_FileDoesNotExists, filename);
                return false;
            }

            #endregion Data Validation

            // Get a list of plugins from the file
            plugins = GetPlugins(filename);

            _log.DebugFormat(Properties.Resources.Msg_FileContainsPlugins_Count, filename, plugins.Count);
            return (plugins.Count != 0);
        }

        /// <summary>
        /// Returns a boolean to indicate if the specified filename contains plug-in of the
        /// <see cref="TaskPluginInterface.PluginType" /> type.
        /// </summary>
        /// <param name="filename">The file name to check for plugins</param>
        /// <param name="pluginType">A <see cref="TaskPluginInterface.PluginType"/> to look for.</param>
        /// <returns>A true if the file has at least one plug-in of the specified plug-in type, otherwise false.</returns>
        public static bool FileContainsPlugins(string filename, PluginType pluginType)
        {
            _log.DebugFormat(Properties.Resources.Msg_FileContainsPlugins_Checking, filename, pluginType);
                        
            #region Variables

            List<string> plugins;

            #endregion Variables

            #region Data Validation

            // Check to see if the file exist
            if (!File.Exists(filename))
            {
                _log.DebugFormat(Properties.Resources.Msg_FileDoesNotExists, filename);
                return false;
            }

            #endregion Data Validation

            // Get a list of plugins from the file
            plugins = GetPlugins(filename, pluginType);

            _log.DebugFormat(Properties.Resources.Msg_FileContainsPlugins_Count, filename, plugins.Count, pluginType);
            return (plugins.Count != 0);
        }

        #endregion FileContainsPlugins

        #region GetPlugins

        /// <summary>
        /// Gets a List of <see cref="TaskPluginInterface.ITaskPlugin"/> from the specified filename.
        /// </summary>
        /// <param name="filename">A file to load the Plugins from</param>
        /// <returns>A list of <see cref="TaskPluginInterface.ITaskPlugin">Plugins </see></returns>
        public static List<string> GetPlugins(string filename)
        {
            _log.DebugFormat(Properties.Resources.Msg_GetPlugins_Checking, filename);

            #region Variables
            Assembly asm;
            List<string> pluginList = new List<string>();
            #endregion Variable

            #region Data Validation

            // Check to see if the file exist
            if (!File.Exists(filename))
            {
                _log.DebugFormat(Properties.Resources.Msg_FileDoesNotExists, filename);
                return pluginList;
            }
            
            #endregion DataValidation

            try
            {
                _log.DebugFormat(Properties.Resources.Msg_GetPlugins_LoadFile, filename);
                asm = Assembly.LoadFile(filename);

                // Loop through all of the type
                foreach (Type type in asm.GetTypes())
                {
                    Type[] interfaces = type.GetInterfaces();
                    foreach (Type iface in interfaces)
                    {
                        if (iface.Name == typeof(ITaskPlugin).Name)
                        {
                            // This type/class is of type TaskPluginInterface.ITaskPlugin
                            // so we shall return it
                            _log.DebugFormat(Properties.Resources.Msg_GetPlugins_FoundInterface, type.FullName, filename);
                            pluginList.Add(type.FullName);
                        }
                    }
                }
            }
            catch (BadImageFormatException)
            {
                // This one we can ignore (most likely the configuration file)
                _log.DebugFormat(Properties.Resources.Msg_NotAValidAssembly, filename);
            }
            
            // Cleanup
            _log.DebugFormat(Properties.Resources.Msg_GetPlugins_Count, filename, pluginList.Count);
            return pluginList;

        }

        /// <summary>
        /// Gets a List of <see cref="TaskPluginInterface.ITaskPlugin"/> from the specified filename and 
        /// <see cref="TaskPluginInterface.PluginType">Plugin type</see>.
        /// </summary>
        /// <param name="filename">A file to load the Plugins from.</param>
        /// <param name="pluginType">A <see cref="TaskPluginInterface.PluginType">plugin type</see>.</param>
        /// <returns>A list of <see cref="TaskPluginInterface.ITaskPlugin">Plugins</see>.</returns>
        public static List<string> GetPlugins(string filename, PluginType pluginType)
        {
            _log.DebugFormat(Properties.Resources.Msg_GetPlugins_Checking, filename, pluginType);

            #region Variables
            Assembly asm;
            List<string> pluginList = new List<string>();
            #endregion Variable

            #region Data Validation

            // Check to see if the file exist
            if (!File.Exists(filename))
            {
                _log.DebugFormat(Properties.Resources.Msg_FileDoesNotExists, filename);
                return pluginList;
            }
            
            #endregion DataValidation

            try
            {
                _log.DebugFormat(Properties.Resources.Msg_GetPlugins_LoadFile, filename);
                asm = Assembly.LoadFile(filename);

                // Loop through all of the type
                foreach (Type type in asm.GetTypes())
                {
                    Type[] interfaces = type.GetInterfaces();
                    foreach (Type iface in interfaces)
                    {
                        if (iface.Name == typeof(ITaskPlugin).Name)
                        {
                            // This type/class is of type TaskPluginInterface.ITaskPlugin
                            _log.DebugFormat(Properties.Resources.Msg_GetPlugins_FoundInterface, type.FullName, filename);
                            // Get the custom attribute to see if it is of the ITaskPlugin
                            // that we want
                            object[] attributes = type.GetCustomAttributes(typeof(PluginAttribute), true);
                            if (attributes.Length != 0)
                            {
                                // An attribute was found
                                foreach (PluginAttribute pluginAttribute in attributes)
                                {
                                    if (pluginAttribute.Type == pluginType)
                                    {
                                        _log.DebugFormat(Properties.Resources.Msg_GetPlugins_FoundInterface1, type.FullName, filename, pluginType);
                                        pluginList.Add(type.FullName);
                                    }
                                }
                            }
                        }
                    }
                }
            }
            catch (BadImageFormatException)
            {
                // This one we can ignore (most likely the configuration file)
                _log.DebugFormat(Properties.Resources.Msg_NotAValidAssembly, filename);
            }

            // Cleanup

            _log.DebugFormat(Properties.Resources.Msg_GetPlugins_Count1, filename, pluginList.Count, pluginType);
            return pluginList;

        }

        #endregion GetPlugins

        #region GetPlugin

        /// <summary>
        /// Returns a <see cref="ITaskPlugin" />
        /// </summary>
        /// <param name="filename">The filename that contains the Plugin</param>
        /// <param name="className">The class name to load</param>
        /// <returns>
        /// A Type that is inherited from <see cref="TaskPluginInterface.ITaskPlugin"/>, 
        /// if available, otherwise null.
        /// </returns>
        public static ITaskPlugin GetPlugin(string filename, string className)
        {
            _log.DebugFormat(Properties.Resources.Msg_GetPlugin_Checking, className, filename);

            #region Variables

            #endregion Variable

            #region Data Validation

            // Check to see if the file exist
            if (!File.Exists(filename))
            {
                _log.DebugFormat(Properties.Resources.Msg_FileDoesNotExists, filename);
                return null;
            }

            #endregion DataValidation

            try
            {
                System.Runtime.Remoting.ObjectHandle obj = Activator.CreateInstanceFrom(filename, className);
                if (obj != null)
                {
                    return (ITaskPlugin) obj.Unwrap();
                }
            }
            catch (BadImageFormatException)
            {
                // This one we can ignore (most likely the configuration file)
                _log.DebugFormat(Properties.Resources.Msg_NotAValidAssembly, filename);
            }

            return null;

        }

        #endregion GetPlugin

        #region GetPluginFolder
        /// <summary>
        /// Returns a fully qualified path to the plug-in directory based on the folder name passed.
        /// </summary>
        /// <param name="folderName">The folder name to try and use as the plug-in folder</param>
        /// <returns>A fully qualified path to the plug-in folder.</returns>
        /// <remarks>First checks to see if the path specified exists, if so returns it.<br></br>
        /// Second, it try to see if the path is relative, if so returns it.<br></br>
        /// Third, it tries to create the directory, if successful it creates and returns the directory.<br></br>
        /// <b>null</b> is returned if all three fail.
        /// </remarks>
        /// 
        public static string GetPluginFolder(string folderName)
        {
            _log.DebugFormat(Properties.Resources.Msg_GetPluginFolder_Checking, folderName);

            #region Variables

            string newFolderName;

            #endregion Variables

            #region Data Validation

            #endregion Data Validation

            // First check to see if the folder passed exists
            if (Directory.Exists(folderName))
            {
                newFolderName = Path.GetFullPath(folderName);
                _log.DebugFormat(Properties.Resources.Msg_GetPluginFolder_Exists, newFolderName);
                return newFolderName;
            }

            // If it does not exist assume the folder specified is a sub folder of the current
            // directory.
            newFolderName = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, folderName);
            _log.DebugFormat(Properties.Resources.Msg_GetPluginFolder_Checking, newFolderName);
            if (Directory.Exists(newFolderName))
            {
                _log.DebugFormat(Properties.Resources.Msg_GetPluginFolder_Exists, newFolderName);
                return newFolderName;
            }

            // if still does not exist try to create it.
            try
            {
                _log.DebugFormat(Properties.Resources.Msg_GetPluginFolder_Create, newFolderName);
                Directory.CreateDirectory(newFolderName);
                return newFolderName;
            }
            catch
            {
                _log.ErrorFormat(Properties.Resources.Msg_GetPluginFolder_Fail, folderName, newFolderName);
                return null;
            }

        }

        #endregion GetPluginFolder

        #region Progress Struct

        /// <summary>
        /// This structure is used for reporting status up to the plug-in Host.
        /// </summary>
        public struct Progress
        {
            #region Private Variables
            private int _totalTasks;
            private int _currentTask;
            private int _totalItems;
            private int _currentItem;
            #endregion Private Variables

            #region Constructor
            
            /// <summary>
            /// Creates a Progress item
            /// </summary>
            /// <param name="currentItem">The current item being processed.</param>
            /// <param name="totalItems">The total number of items to process.</param>
            /// <param name="currentTask">The current task being processed. (Zero based)</param>
            /// <param name="totalTasks">The total number of tasks to process.</param>
            public Progress(int currentItem, int totalItems, int currentTask, int totalTasks)
            {
                
                _currentItem = currentItem;
                _totalItems = totalItems;
                _currentTask = currentTask;
                _totalTasks = totalTasks;
                
            }

            #endregion Constructor

            #region Public Variables
            /// <summary>
            /// Indicates the total number of task that the plug-in will execute.<br></br>
            /// This will be used for reporting progress.
            /// </summary>
            public int TotalTasks
            { 
                get { return _totalTasks; }
                set { _totalTasks = value; }
            }

            /// <summary>
            /// Indicates the current task that the plug-in is processing.<br></br>
            /// This will be used for reporting progress.
            /// </summary>
            public int CurrentTask
            {
                get { return _currentTask; }
                set { _currentTask = value; }
            }

            /// <summary>
            /// Indicates the total number of item that the plug-in will process for the current
            /// task.<br></br>
            /// This will be used for reporting progress.
            /// </summary>
            public int TotalItems
            {
                get { return _totalItems; }
                set { _totalItems = value; }
            }

            /// <summary>
            /// Indicates the current item that the plug-in is processing.<br></br>
            /// This will be used for reporting progress.
            /// </summary>
            public int CurrentItem
            {
                get { return _currentItem; }
                set { _currentItem = value; }
            }

            #endregion Public Variables
        }

        #endregion Progess Struct

    }
}
